package com.xframe.xdal.core.domain;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.sql.SqlUtil;
import com.alibaba.fastjson2.JSON;
import com.xframe.xdal.core.constant.ErrorCode;
import com.xframe.xdal.core.constant.ErrorMsg;
import com.xframe.xdal.core.exception.DalFrameException;
import com.xframe.xdal.core.model.IModel;
import com.xframe.xdal.core.model.mapping.IColumnMapping;
import com.xframe.xdal.core.model.mapping.ITableInfo;
import com.xframe.xdal.core.serialize.JsonConfig;
import com.xframe.xdal.core.serialize.JsonConfigContainer;
import com.xframe.xdal.core.serialize.JsonModel;
import com.xframe.xdal.core.serialize.proc.ISerializeProc;
import com.xframe.xdal.core.util.StringUtil;
import com.xframe.xdal.core.util.XReflect;
import org.apache.poi.ss.formula.functions.T;

import java.nio.charset.Charset;
import java.sql.*;
import java.util.Date;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 功能描述: 动态实体对象
 *
 * @author xufeng
 * @version 1.0
 * @date 2023-03-20
 */
public class XDynamicModel extends XDynamicDict implements IModel, IDynamicModel {

    //private ITableInfo tableInfo;


    public static final String PK_NAME = "keyId";

    private ITableInfo tableInfo;


    public XDynamicModel() {
        super();
    }

    public XDynamicModel(ITableInfo tableInfo) {
        super();
        this.tableInfo = tableInfo;
    }

    public XDynamicModel(Map<String, Object> m) {

        super((Map)(null == m ? new HashMap() : m));
    }

    public XDynamicModel(ITableInfo tableInfo, Map<String, Object> map) {
        super(map);
        this.tableInfo = tableInfo;
    }

    public static XDynamicModel create(ITableInfo tableInfo, Map<String, Object> map){
        try {
            XDynamicModel xDynamicModel = new XDynamicModel(tableInfo, map);
            return xDynamicModel;
        }catch (Exception exception){
            exception.printStackTrace();
            return null;
        }
    }

    public static <T> XDynamicModel create(T bean){
        XDynamicModel xDynamicModel = new XDynamicModel();
        xDynamicModel.parseBean(bean);
        return xDynamicModel;
    }

    private Set<String> fieldNames;

    public static XDynamicModel createWithConfig(ITableInfo tableInfo, Map<String, Object> map, JsonConfig jsonConfig) {
        if(ObjectUtil.isEmpty(jsonConfig)){
            return create(tableInfo,map);
        }
        return create(tableInfo,createChildValue(jsonConfig,map));
    }

    public <T> T toDomain(Class<T> clazz){
        return (T)JSON.to(clazz,this);
    }

    private static Map<String, Object> createChildValue(JsonConfig jsonConfig, Map<String, Object> map) {
        Map<String, Object> data = new HashMap<>();
        Map<String, JsonModel> jsonConfigMap = jsonConfig.getJsonModelList().stream().collect(Collectors.toMap(JsonModel::getMapName, Function.identity()));
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            //跳过没有的配置
            if (!jsonConfigMap.containsKey(entry.getKey())) {
                data.put(entry.getKey(), entry.getValue());
                continue;
            }
            Object value = entry.getValue();
            JsonModel jsonModel = jsonConfigMap.get(entry.getKey());
            if (jsonModel.isRefModel()) {
                JsonConfig tempJsonConfig = JsonConfigContainer.getConfigContainer().getJsonConfig(jsonConfig.getLanguage(), jsonConfig.getVer().toString(), jsonModel.getRefModelCode(), jsonModel.getFlag());
                if (jsonModel.isRefArray()) {




//                    if(value instanceof List){
//                        List collection = (List)value;
//                        for (int i = 0; i < collection.size(); i++) {
//                            collection.set(i,createChildValue(tempJsonConfig, (Map<String, Object>) collection.get(i)));
//                        }
//                    }

                    List children = (List) entry.getValue();
                    //Map<String, Object>[] values = new HashMap[children.size()];
                    for (int i = 0; i < children.size(); i++) {
                        children.set(i,createChildValue(tempJsonConfig, (Map<String, Object>) children.get(i)));
                    }
                    //value = values;
                }else if (jsonModel.isRefObj()) {
                    Map<String, Object> child = (Map<String, Object>) entry.getValue();
                    if(tempJsonConfig != null) {
                        value = createChildValue(tempJsonConfig, child);
                    }
                }
            }

            Object finalValue = value;
            Optional.ofNullable(jsonModel.getSerializeProc()).ifPresent(e->e.procValue(map, finalValue,jsonModel.getClassArgs(),jsonConfig.getLanguage()));

            data.put(jsonModel.getName(),value);
        }
        return data;
    }
    @Override
    public void setModelId(ITableInfo tableInfo){
        this.tableInfo = tableInfo;
    }

    @Override
    public ITableInfo getModelId(){
        return this.tableInfo;
    }

    public Set<String> getFieldNames() {
        return this.fieldNames;
    }

    public XDynamicModel setFieldNames(Collection<String> fieldNames) {
        if (CollectionUtil.isNotEmpty(fieldNames)) {
            this.fieldNames = CollectionUtil.newHashSet(true, fieldNames);
        }
        return this;
    }

    public XDynamicModel setFieldNames(String... fieldNames) {
        if (ArrayUtil.isNotEmpty(fieldNames)) {
            this.fieldNames = CollectionUtil.newLinkedHashSet(fieldNames);
        }
        return this;
    }

    public XDynamicModel addFieldNames(String... fieldNames) {
        if (ArrayUtil.isNotEmpty(fieldNames)) {
            if (null == this.fieldNames) {
                return this.setFieldNames(fieldNames);
            }
            Collections.addAll(this.fieldNames, fieldNames);
        }
        return this;
    }

    public <T> XDynamicModel parseBean(T bean) {
        return (XDynamicModel)super.parseBean(bean);
    }


    public XDynamicModel set(String field, Object value) {
        return set(field,value,true);
    }

    public XDynamicModel set(String field, Object value,boolean checkFild) {
        try {
            if (this.tableInfo != null) {

                Class<?> fieldType = value == null ? String.class : value.getClass();
                if(checkFild) {
                    IColumnMapping columnMapping = this.tableInfo.findOneByFieldName(field);
                    if (columnMapping == null) {
                        throw new DalFrameException(ErrorCode.COLUMNMAPPING_NOT_FIND, ErrorMsg.COLUMNMAPPING_NOT_FIND_MSG);
                    }
                    fieldType = columnMapping.getDataType();
                }
                Object val = StringUtil.ObjectConvert(fieldType, value);
                super.set(field, val);
            } else {
                super.set(field, value);
            }
            return this;
        }catch (DalFrameException dalFrameException){
            throw dalFrameException;
        }
    }

    public XDynamicModel setIgnoreNull(String field, Object value) {
        return (XDynamicModel)super.setIgnoreNull(field, value);
    }

    public Clob getClob(String field) {
        return (Clob)this.get(field, (Object)null);
    }

    public Blob getBlob(String field) {
        return (Blob)this.get(field, (Object)null);
    }

    public Time getTime(String field) {
        Object obj = this.get(field);
        Time result = null;
        if (null != obj) {
            try {
                result = (Time)obj;
            } catch (Exception var5) {
                result = (Time) XReflect.invoke(obj, "timeValue", new Object[0]);
            }
        }

        return result;
    }

    public Date getDate(String field) {
        Object obj = this.get(field);
        Date result = null;
        if (null != obj) {
            try {
                result = (Date)obj;
            } catch (Exception var5) {
                result = (Date)XReflect.invoke(obj, "dateValue", new Object[0]);
            }
        }

        return result;
    }

    public Timestamp getTimestamp(String field) {
        Object obj = this.get(field);
        Timestamp result = null;
        if (null != obj) {
            try {
                result = (Timestamp)obj;
            } catch (Exception var5) {
                result = (Timestamp)XReflect.invoke(obj, "timestampValue", new Object[0]);
            }
        }

        return result;
    }

    public String getStr(String field) {
        return this.getStr(field, CharsetUtil.CHARSET_UTF_8);
    }

    public String getStr(String field, Charset charset) {
        Object obj = this.get(field);
        if (obj instanceof Clob) {
            return SqlUtil.clobToStr((Clob)obj);
        } else if (obj instanceof Blob) {
            return SqlUtil.blobToStr((Blob)obj, charset);
        } else if (obj instanceof RowId) {
            RowId rowId = (RowId)obj;
            return StrUtil.str(rowId.getBytes(), charset);
        } else {
            return super.getStr(field);
        }
    }

    public RowId getRowId() {
        return this.getRowId("ROWID");
    }

    public RowId getRowId(String field) {
        Object obj = this.get(field);
        if (null == obj) {
            return null;
        } else if (obj instanceof RowId) {
            return (RowId)obj;
        } else {
            throw new DalFrameException(StringUtil.format("Value of field [{}] is not a rowid!", new Object[]{field}));
        }
    }

    public XDynamicModel clone() {
        return (XDynamicModel)super.clone();
    }


    @Override
    public Object getObjectId() {
        if(this.tableInfo != null){
            return this.getLong(this.tableInfo.findPkFirst().getFieldName());
        }
        return this.getLong(PK_NAME);
    }

    @Override
    public Object getMapkey(){
        return this.getObjectId();
    }

    @Override
    public String getEsId() {
        return getObjectId().toString();
    }

    @Override
    public void setObjectId(Object objectId) {
        if (this.tableInfo != null) {
            this.set(this.tableInfo.findPkFirst().getFieldName(), objectId);
        } else {
            this.set(PK_NAME, objectId);
        }
    }

    @Override
    public String findPkField() {
        if(this.tableInfo != null){
            return this.tableInfo.findPkFirst().getFieldName();
        }
        return PK_NAME;
    }

    @Override
    public boolean equals(Object anObject) {
        if (anObject instanceof IModel){
            IModel model = (IModel)anObject;
            return this.getObjectId().equals(model.getObjectId());
        }else {
            return false;
        }
    }

    @Override
    public int hashCode(){
        return this.getObjectId().hashCode();
    }
}
